#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Selection.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A Selection is a primitive Component
#               to represent multiple simple true/false Options
#               in an assessment.  It is a container for Options (only)
#               and permits both single and multilpe selections of options
#               with a variety of display formats.  Unlike the 
#               MultipleChoice component, it does not support the
#               concept of correctness.
#
# See Also:     Component, Model, Option
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.KERLProcessor import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.Graphing.Graphable import *
from Products.KnowledgeEngine.Graphing.Datum import *
from Products.KnowledgeEngine.User import User 
from Products.KnowledgeEngine.Caching import RequestCache 

#####################################################################
## Zope CMF Library Imports
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import CMFCorePermissions
from Products.CMFCore.CMFCorePermissions import ModifyPortalContent

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## SelectionInfo Class Definition
class SelectionInfo(ComponentInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Return the class name
        """
        return 'Selection'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Selection"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "sel"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/Selection_icon.gif', globals())

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return true          

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Selections may have Option children
        return childClassInfo.isInstanceOf('Option')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)

        #add the 'displayFormat' property        
        self.addPropertyDefinition(PropertyDefinition(identity="displayFormat", displayName="Display As", description="How options are to be selected/displayed?", propertyType=pt_STRING, propertyFormat=pf_NATIVE, hasEnumeratedValues=true, enumeratedValuesList=['Radio Buttons','Dropdown List','Check Boxes'], defaultValue="Radio Buttons"))

    #---------------------------------------------------------------#

#####################################################################
## Selection Class Definition
class Selection(Component):
    """
        The Option Component Container
    """

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = SelectionInfo()
   
    #---------------------------------------------------------------#

    DISPLAY_FORMAT_OPTIONS = ['Radio Buttons','Dropdown List','Check Boxes']

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Selection"        

        Component.__init__(self, id, identity)
        
    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.
            
            This is to allow the 'Order' column in the Content
            management tab in Zope            
        """

        return true

    #---------------------------------------------------------------#

    def getDisplayFormat(self):
        "Return the display format of the selection"
        
        return self.displayFormat

    #---------------------------------------------------------------#

    def setDisplayFormat(self, displayFormat):
        "Set the display format of the selection"
        
        self.displayFormat = displayFormat

    #---------------------------------------------------------------#

    def isMultipleSelectionPermitted(self):
        """
            Return if multiple option selection is permitted
        """
        
        return self.displayFormat == 'Check Boxes'
    
    #---------------------------------------------------------------#

    def onBeforeRenderAssessmentEditor(self, request, **args):
        """
            This method is called by Component.renderAssessmentEditor
            prior to actually rendering the assessment editor
            for this component using the Page Template (defined
            for this component).
            
            By overriding this method, individual components 
            have the ability to modify the request (add state, 
            declarations etc) prior to rendering occuring.
            
            If you override this method, a call should be 
            made to the super-class to ensure correct
            super-class initialisation.
        """

        #call the super-class handler first
        Component.onBeforeRenderAssessmentEditor(self, request, **args)
        
        #for non-dropdown formated components, we need to add the appropriate
        #javascript declarations, initialisation and refresh statements for each of the options
        if not request['ISPREVIEW'] and self.getDisplayFormat() != 'Dropdown List':            
            for option in self.getChildren():
                #add the javascript declarations and initialisation for this option
                option.addJavaScriptOnLoadStatements(request['ONLOAD_JAVASCRIPT'])
                option.addJavaScriptDeclarations(request['DECLARATIONS_JAVASCRIPT'])        

                #add the showif refresh javascript for each child as necessary
                if option.hasShowIf():
                    request['REFRESH_JAVASCRIPT'].append(option.getShowIfJavaScript(request['ASSESSMENT']))

    #---------------------------------------------------------------#

    def generateGraphableStatistics(self, baseAssessmentFilter = None, user = None, nrAssessments = None):
        """
            Generates a Graphable instance containing statistic
            information for this component - that may be graphed.
            
            The provided assessmentFilter is used as a basis for
            generating statistics information.  Setting this to
            None (the default) means all assessments are used.
            
            RETURN TYPE: Graphable
        """
        cache = RequestCache('Selection.generateGrapableStatistics', self.REQUEST)

        # Build a uniqe cache id
        modelIdentity = baseAssessmentFilter.getModelIdentity()
        for singleScope in baseAssessmentFilter.getAssessmentScope():
            singleScope += singleScope

        if type(user) == User:
            authenticatedUserIdentity = user.getIdentity()
        else:
            authenticatedUserIdentity = user

        expression = baseAssessmentFilter.expression

        c_hash = self.getIdentity() + `modelIdentity` + `expression` + `singleScope` + `authenticatedUserIdentity`

        if c_hash in cache:
            return cache[c_hash]
 
        #NOTE: This only applies for selections that are displayed as radio buttons or dropdown lists
        
        #ensure the baseAssessmentFilter is valid (not None)
        if baseAssessmentFilter is None:
            baseAssessmentFilter = self.getModel().getAllAssessmentsFilter()
        
        #we need to use the assessment storage service to get the statistics
        assessmentStorageService = self.getModel().getAssessmentStorageService()

        #determine the number of assessments (if necessary)
        if nrAssessments is None:
            nrAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(baseAssessmentFilter, user)   
        
        #we need a Graphable for the results
        graphable = Graphable(self.getTitleOrId(), "The following chart outlines the selection statistics for the '%s' component in the %d current assessments." % (self.getTitleOrId(), nrAssessments), 0, nrAssessments)
        
        #create a datum for the number of times each option has been selected in the assessments
        for option in self.getChildren():

            #create a filter for the option
            filterTitle = option.getTitleOrId()
            filterExpression = parseKERL("['%s', 'selected']" % option.getIdentity())
            assessmentFilter = AssessmentFilter(title=filterTitle, expression=simplifyKERL(['all', baseAssessmentFilter.getExpression(), filterExpression]), model=self.getModel(), assessmentScope=baseAssessmentFilter.getAssessmentScope())
            
            #filter the assessments
            nrSelectedAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(assessmentFilter, user)
            datum = Datum(identity=option.getIdentity(), title="# %s" % option.getTitleOrId(), value=nrSelectedAssessments, color="green", footnote="", context={"ASSESSMENT_FILTER":assessmentFilter})
            graphable.addDatum(datum)
            
            #remove the actual selections to work out the undefined count
            nrAssessments = nrAssessments - nrSelectedAssessments

        #create a datum for the number of times no options have been made in the assessments
        datum = Datum(identity="undefined", title="# Undefined.", value=nrAssessments, color="orange", footnote="As it is possible for an end-user to avoid selecting an option, some assessments may have 'undefined' selections.")        
        graphable.addDatum(datum)
        
        cache[c_hash] = graphable 
        return graphable

    #---------------------------------------------------------------#

    def listActions(self, info=None):
        """
            PLONE Support:

            Returns a tuple of ActionInformation instances
            specifying the available CMF Actions for the object

            overrides:

            (CMF Actions are different from Knowledge Engine Actions)
        """

        #add the super-class actions first
        lCMFActions = Component.listActions(self, info)

        #add the 'statistics' action
        lCMFActions = lCMFActions + (
                        ActionInformation(
                            id='statistics',
                            title='statistics',
                            description='Current Statistics',
                            category='object',
                            condition='',
                            permissions=(CMFCorePermissions.ListFolderContents,),
                            priority=1,
                            visible=1,
                            action='string:${object_url}/Selection_Statistics'
                        ),)
                        
        return lCMFActions

    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(Selection)

#####################################################################
